/*
 *	Simple Limiter (runtime function)
 *
 *  File		: SimpleLimitProcess.inl
 *	Library		: SimpleSource
 *  Version		: 1.12
 *  Implements	: void SimpleLimit::process( double &in1, double &in2 )
 *
 *	2006, ChunkWare Music Software, OPEN-SOURCE
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a
 *	copy of this software and associated documentation files (the "Software"),
 *	to deal in the Software without restriction, including without limitation
 *	the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *	and/or sell copies of the Software, and to permit persons to whom the
 *	Software is furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 *	THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *	DEALINGS IN THE SOFTWARE.
 */


#ifndef __SIMPLE_LIMIT_PROCESS_INL__
#define __SIMPLE_LIMIT_PROCESS_INL__

namespace chunkware_simple
{
	//-------------------------------------------------------------
	INLINE void SimpleLimit::process( double &in1, double &in2 )
	{
		// create sidechain

		double rect1 = fabs( in1 );	// rectify input
		double rect2 = fabs( in2 );

		double keyLink = std::max( rect1, rect2 );	// link channels with greater of 2

		// threshold
		// we always want to feed the sidechain AT LEATS the threshold value
		if ( keyLink < thresh_ )
			keyLink = thresh_;

		// test:
		// a) whether peak timer has "expired"
		// b) whether new peak is greater than previous max peak
		if ( (++peakTimer_ >= peakHold_) || (keyLink > maxPeak_) ) {
			// if either condition is met:
			peakTimer_ = 0;		// reset peak timer
			maxPeak_ = keyLink;	// assign new peak to max peak
		}

		/* REGARDING THE MAX PEAK: This method assumes that the only important
		 * sample in a look-ahead buffer would be the highest peak. As such,
		 * instead of storing all samples in a look-ahead buffer, it only stores
		 * the max peak, and compares all incoming samples to that one.
		 * The max peak has a hold time equal to what the look-ahead buffer
		 * would have been, which is tracked by a timer (counter). When this
		 * timer expires, the sample would have exited from the buffer. Therefore,
		 * a new sample must be assigned to the max peak. We assume that the next
		 * highest sample in our theoretical buffer is the current input sample.
		 * In reality, we know this is probably NOT the case, and that there has
		 * been another sample, slightly lower than the one before it, that has
		 * passed the input. If we do not account for this possibility, our gain
		 * reduction could be insufficient, resulting in an "over" at the output.
		 * To remedy this, we simply apply a suitably long release stage in the
		 * envelope follower.
		 */

		// attack/release
		if ( maxPeak_ > env_ )
			att_.run( maxPeak_, env_ );		// run attack phase
		else
			rel_.run( maxPeak_, env_ );		// run release phase

		/* REGARDING THE ATTACK: This limiter achieves "look-ahead" detection
		 * by allowing the envelope follower to attack the max peak, which is
		 * held for the duration of the attack phase -- unless a new, higher
		 * peak is detected. The output signal is buffered so that the gain
		 * reduction is applied in advance of the "offending" sample.
		 */

		/* NOTE: a DC offset is not necessary for the envelope follower,
		 * as neither the max peak nor envelope should fall below the
		 * threshold (which is assumed to be around 1.0 linear).
		 */

		// gain reduction
		double gR = thresh_ / env_;

		// unload current buffer index
		// ( cur_ - delay ) & mask_ gets sample from [delay] samples ago
		// mask_ variable wraps index
		unsigned int delayIndex = ( cur_ - peakHold_ ) & mask_;
		double delay1 = outBuffer_[ 0 ][ delayIndex ];
		double delay2 = outBuffer_[ 1 ][ delayIndex ];

		// load current buffer index and advance current index
		// mask_ wraps cur_ index
		outBuffer_[ 0 ][ cur_ ] = in1;
		outBuffer_[ 1 ][ cur_ ] = in2;
		++cur_ &= mask_;

		// output gain
		in1 = delay1 * gR;	// apply gain reduction to input
		in2 = delay2 * gR;

		/* REGARDING THE GAIN REDUCTION: Due to the logarithmic nature
		 * of the attack phase, the sidechain will never achieve "full"
		 * attack. (Actually, it is only guaranteed to achieve 99% of
		 * the input value over the given time constant.) As such, the
		 * limiter cannot achieve "brick-wall" limiting. There are 2
		 * workarounds:
		 *
		 * 1) Set the threshold slightly lower than the desired threshold.
		 *		i.e. 0.0dB -> -0.1dB or even -0.5dB
		 *
		 * 2) Clip the output at the threshold, as such:
		 *
		 *		if ( in1 > thresh_ )		in1 = thresh_;
		 *		else if ( in1 < -thresh_ )	in1 = -thresh_;
		 *
		 *		if ( in2 > thresh_ )		in2 = thresh_;
		 *		else if ( in2 < -thresh_ )	in2 = -thresh_;
		 *
		 *		(... or replace with your favorite branchless clipper ...)
		 */
	}

}	// end namespace chunkware_simple

#endif	// end __SIMPLE_LIMIT_PROCESS_INL__
